package middleware

import (
	"bytes"
	"devops/config"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"sync"
	"time"

	"github.com/gin-gonic/gin"
)

var tsfUserCache = tsfUsers{Users: make(map[string]User)}

type tsfUsers struct {
	Users map[string]User
	sync.RWMutex
}

type User struct {
	AppID          string      `json:"AppId"`
	SubAccountUin  string      `json:"SubAccountUin"`
	Uin            string      `json:"Uin"`
	UserID         string      `json:"UserId"`
	UserName       string      `json:"UserName"`
	UserType       string      `json:"UserType"`
	Phone          interface{} `json:"Phone"`
	Email          interface{} `json:"Email"`
	Lock           string      `json:"Lock"`
	AccountID      string      `json:"AccountId"`
	CreationTime   int         `json:"CreationTime"`
	LastUpdateTime int         `json:"LastUpdateTime"`
	DeleteFlag     interface{} `json:"DeleteFlag"`
	AppName        string      `json:"AppName"`
	IsAdmin        string      `json:"IsAdmin"`
	Expires        int64       `json:"expired"`
}

type UserResponse struct {
	Response UserResult `json:"Response"`
}

type UserResult struct {
	RequestID interface{} `json:"RequestId"`
	Result    User        `json:"Result"`
}

var TsfFailRespRaw = `{
    "Response": {
        "RequestId": null,
        "Error": {
            "Message": "账号未登录/会话失效",
            "Code": "UnauthorizedOperation.SsoExpiredCredential"
        }
    }
}`

type CheckTokenResponse struct {
	Response Response `json:"Response"`
}
type Result struct {
	AppID         string `json:"AppId"`
	SubAccountUin string `json:"SubAccountUin"`
	Uin           string `json:"Uin"`
	Token         string `json:"Token"`
	UserID        string `json:"UserId"`
}
type Response struct {
	RequestID interface{} `json:"RequestId"`
	Result    Result      `json:"Result"`
	Error     Error       `json:"Error"`
}

type Error struct {
	Message string `json:"Message"`
	Code    string `json:"Code"`
}

func TsfAuth() gin.HandlerFunc {
	return func(c *gin.Context) {
		token, err := c.Cookie("accessToken")
		if err != nil {
			c.String(http.StatusOK, TsfFailRespRaw)
			c.Abort()
			return
		}
		user, raw, err := checkAuth(token)
		if err != nil || user == nil {
			c.String(http.StatusOK, raw)
			c.Abort()
			return
		}

		c.Set("curUser", *user)
		c.Next()
	}
}

func checkAuth(token string) (*User, string, error) {
	tokenResp, raw, err := checkToken(token)
	if err == nil && tokenResp != nil {
		// token 验证成功后查询缓存有没有 user 信息， 没有则请求接口获取 user 信息
		user := checkauthcache(token)
		if user.Expires != 0 && user.Expires < time.Now().Unix() {
			return &user, "", nil
		}
		curUser, err := GetTsfCurrentUser(tokenResp)
		if err != nil {
			return nil, raw, err
		}
		go cacheUser(token, *curUser)
		return curUser, raw, nil
	}
	return nil, raw, err
}

func GetTsfCurrentUser(check *CheckTokenResponse) (*User, error) {
	postBody := fmt.Sprintf(`{"subAccountUin":"%s", "appId": "%s"}`, check.Response.Result.SubAccountUin, check.Response.Result.AppID)
	resp, err := http.Post(config.TsfUrl+"/user/current", "application/json;charset=utf-8", bytes.NewBufferString(postBody))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	userResp := UserResponse{}
	json.Unmarshal(content, &userResp)
	user := userResp.Response.Result
	user.Expires = time.Now().Unix() + 7200
	return &user, nil
}

func checkToken(token string) (*CheckTokenResponse, string, error) {
	postBody := fmt.Sprintf(`{"token":"%s"}`, token)
	resp, err := http.Post(config.TsfUrl+"/authc/checkToken", "application/json;charset=utf-8", bytes.NewBufferString(postBody))
	if err != nil {
		return nil, "", err
	}
	defer resp.Body.Close()
	content, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, "", err
	}
	tsf := CheckTokenResponse{}
	json.Unmarshal(content, &tsf)
	if tsf.Response.Error != (Error{}) {
		return nil, string(content), nil
	}
	return &tsf, string(content), nil
}

func checkauthcache(token string) User {
	tsfUserCache.RLock()
	defer tsfUserCache.RUnlock()
	user, ok := tsfUserCache.Users[token]
	if ok {
		return user
	}
	return User{}
}

func cacheUser(token string, user User) {
	tsfUserCache.Lock()
	defer tsfUserCache.Unlock()
	tsfUserCache.Users[token] = user
}
